package com.hframe.database;


import com.hframe.FrameInit;
import com.hframe.data.DRef;
import com.hframe.type.T;
import com.hframe.type.TArray;
import com.hframe.type.TData;
import com.hframe.type.TEnum;
import com.hframe.typefilter.DateToString;
import org.apache.log4j.Logger;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;

public abstract class Database {
    private static Logger log = Logger.getLogger(FrameInit.class);


    private static final String TAG = Database.class.getName();
    private static boolean allDebug = false;
    protected boolean debug = allDebug;
    private StringBuffer strSql = null;
    private boolean isOccupy = false;
    private boolean isThread = false;
    private DatabasePool.Pool pool;

    public abstract DatabaseConfig getConfig();

    protected abstract boolean getConnection() throws Exception;

    protected abstract boolean createStatement(boolean autoCommit) throws Exception;

    protected abstract boolean closeConnection();

    protected abstract boolean closeStatement();

    public abstract void setAutoCommit(boolean paramBoolean) throws Exception;

    public abstract boolean getAutoCommit() throws Exception;

    public abstract void commit() throws Exception;

    public abstract Savepoint setSavepoint() throws Exception;

    public abstract void rollback(Savepoint paramSavepoint) throws Exception;

    public abstract void releaseSavepoint(Savepoint paramSavepoint) throws Exception;

    public abstract void rollback();

    protected abstract int execute(String sql) throws Exception;

    protected abstract int query(String sql, TArray<? extends DRef> datas, int count) throws Exception;

    public boolean isDebug() {
        return debug;
    }

    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    public int exeSql(Sql sql) throws Exception {
        return exeSql(sql, new TArray<DRef>(DRef.class), -1);
    }

    public int exeSql(Sql sql, TData<? extends DRef> data) throws Exception {
        TArray<? extends DRef> array = new TArray<DRef>((Class<DRef>) data.getClassName());
        if (0 < exeSql(sql, array, 1)) {
            Field field = T.class.getDeclaredField("object");
            field.setAccessible(true);
            field.set(data, array.get(0));
            return 1;
        }
        return 0;
    }


    public int exeSql(Sql sql, TArray<? extends DRef> array) throws Exception {
        return exeSql(sql, array, -2);
    }

    public int exeSql(Sql sql, TArray<? extends DRef> array, int count) throws Exception {
        if (null == sql)
            return 0;
        strSql = new StringBuffer();
        SqlTemplate template = sql.getTemplate();
        compileString(strSql, template.get(), template.parse(), 0, sql.getParams(), null);


        if (6 < strSql.length() && "select".equals(strSql.substring(0, 6).toLowerCase()))
            return query(strSql.toString(), array, count);
        else
            return execute(strSql.toString());
    }

    protected int compileString(StringBuffer strSql, String sql, SqlTemplate.Param param, int start, Map<String, Object> mapParam, Object defaultValue) throws Exception {
        String name;
        Object value = null;


        strSql.append(sql.substring(start, param.start));
        start = param.start;
        if ('{' == param.tag) {
            if (-1 == param.point && -1 == param.pointEnd) {
                name = sql.substring(param.start + 1, param.end);
                if ("?".equals(name)) {
                    value = defaultValue;
                } else {
                    value = mapParam.get(name);
                }
                waitTypeValue(strSql, value, null);
                start = param.end;

            } else if (-1 != param.point && -1 != param.pointEnd) {
                name = sql.substring(param.start + 1, param.point);
                if ("?".equals(name)) {
                    value = defaultValue;
                } else {
                    value = mapParam.get(name);
                }

                name = sql.substring(param.point + 1, param.pointEnd);
                if (!"?".equals(name)) {
                    value = getypeValue(value, name);
                }

                compileObjcet(strSql, sql, param, mapParam, name, value, param.params.get(0));
                start = param.end;
            } else {
                name = sql.substring(param.start + 1, param.point != -1 ? param.point : param.pointEnd);
                if ("?".equals(name)) {
                    value = defaultValue;
                } else {
                    value = mapParam.get(name);
                }
                if (0 < param.params.size()) {

                    compileObjcet(strSql, sql, param, mapParam, name, value, param.params.get(0));
                    start = param.end;
                } else {
                    if (value instanceof DRef)
                        getDataValue(strSql, (DRef<?>) value, sql.substring(param.point + 1, param.end));

                    else if (null != value && (value instanceof TData))
                        getDataValue(strSql, (DRef<?>) ((TData) value).get(), sql.substring(param.point + 1, param.end));

                    else
                        strSql.append("null");
                    start = param.end;
                }
            }
        } else if ('`' == param.tag) {
            addColumnName(strSql, true);
            if (0 >= param.params.size()) {
                strSql.append(sql.substring(start + 1, param.end));
            } else {
                start = param.start + 1;
                for (SqlTemplate.Param item : param.params) {
                    start = compileString(strSql, sql, item, start, mapParam, defaultValue);
                }
            }
            addColumnName(strSql, false);
            return param.end + 1;
        } else {
            if ('[' == param.tag)
                start = param.start + 1;

            for (SqlTemplate.Param item : param.params) {
                start = compileString(strSql, sql, item, start, mapParam, defaultValue);
            }
        }
        strSql.append(sql.substring(start, param.end));
        return param.end + 1;
    }

    private void compileObjcet(StringBuffer strSql, String sql, SqlTemplate.Param param, Map<String, Object> mapParam, String name, Object value, SqlTemplate.Param temp) throws Exception {
        int start;
        if (null != value) {
            if (value instanceof TArray) {
                ArrayList<?> array = ((TArray<?>) value).get();
                compileArray(strSql, sql, param, mapParam, temp, array);
                return;

            } else if (value instanceof ArrayList) {
                compileArray(strSql, sql, param, mapParam, temp, ((ArrayList) value));
                return;

            } else if (value instanceof Object[]) {
                Object[] array = (Object[]) value;
                for (int i = 0; i < array.length; i++) {
                    start = compileString(strSql, sql, temp, temp.start, mapParam, array[i]);
                    if (i < array.length - 1)
                        strSql.append(sql.substring(start, param.end));
                }
                return;

            } else if (value instanceof T)
                if (((T) value).isEmpty())
                    return;

            compileString(strSql, sql, temp, temp.start, mapParam, value);
        }
    }

    private void compileArray(StringBuffer strSql, String sql, SqlTemplate.Param param, Map<String, Object> mapParam, SqlTemplate.Param temp, ArrayList<?> array) throws Exception {
        int start;
        for (int i = 0; i < array.size(); i++) {
            start = compileString(strSql, sql, temp, temp.start, mapParam, array.get(i));
            if (i < array.size() - 1)
                strSql.append(sql.substring(start, param.end));
        }
    }

    protected void addColumnName(StringBuffer strSql, boolean b) {
        if (b)
            strSql.append("[");
        else
            strSql.append("]");
    }

    protected Object getypeValue(Object value, String key) throws Exception {
        if (null == value || null == key)
            return null;

        if (value instanceof TData<?>)
            value = ((TData<?>) value).get();

        if (null == value)
            return null;

        if (!(value instanceof DRef<?>))
            return null;

        Class<?> cla = value.getClass();
        while (null != cla && cla != DRef.class) {
            for (Field field : cla.getDeclaredFields()) {
                field.setAccessible(true);
                try {
                    if (!isExtendsClass(field.getType(), T.class))
                        continue;

                    String fieldName = field.getName();
                    Object object = field.get(value);

                    if (null != object) {
                        SqlFiletr filetr = ((T<?>) object).getfilter(SqlFiletr.class);
                        if (null != filetr) {
                            if (key.endsWith(filetr.getName())) {
                                return filetr.get();
                            }
                            continue;
                        }
                    }

                    if (key.equals(fieldName)) {
                        return object;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            cla = cla.getSuperclass();
        }
        return null;
    }

    protected boolean waitTypeValue(StringBuffer strSql, Object value, String key) throws Exception {
        if (null == value) {
            strSql.append(value);
        } else {

            if (value instanceof T) {
                SqlFiletr filetr = ((T<?>) value).getfilter(SqlFiletr.class);
                if (null != filetr) {
                    if (null == key || (null != key && key.equals(filetr.getName()))) {
                        if (filetr.getReadWait() == SqlFiletr.ReadWait.OnlyWait || filetr.getReadWait() == SqlFiletr.ReadWait.NotReadWait) {
                            strSql.append("null");

                        } else if (null != filetr.getSql()) {
                            SqlTemplate template = filetr.getSql().getTemplate();
                            compileString(strSql, template.get(), template.parse(), 0, filetr.getSql().getParams(), null);

                        } else {
                            waitTypeValue(strSql, filetr.get(), null);
                        }
                        return true;
                    }
                } else if (null == key) {
                    waitTypeValue(strSql, ((T<?>) value).get(), null);

                }
            } else if (value instanceof String) {
                formatValue(strSql, (String) value);

            } else if (value instanceof Date) {
                formatValue(strSql, DateToString.format((Date) value, DateToString.DATE_TIME_LINE));

            } else if (value instanceof Number) {
                strSql.append(value);

            } else if (value instanceof Enum) {
                strSql.append(((Enum) value).ordinal());

            } else if (value instanceof Sql) {
                SqlTemplate template = ((Sql) value).getTemplate();
                compileString(strSql, template.get(), template.parse(), 0, ((Sql) value).getParams(), null);

            } else if (value instanceof Boolean) {
                strSql.append(value.toString());
            } else {
                formatValue(strSql, value.toString());
            }
        }
        return false;
    }

    private void formatValue(StringBuffer strSql, String value) {
        if ('`' == strSql.charAt(strSql.length() - 1))
            strSql.append(value);
        else
            strSql.append("'").append(value.replace("\\", "\\\\").replace("'", "\\'")).append("'");
    }

    protected void getDataValue(StringBuffer strSql, DRef<?> value, String name) {
        Class<?> cla = value.getClass();
        while (null != cla && cla != DRef.class) {
            for (Field field : cla.getDeclaredFields()) {
                field.setAccessible(true);
                try {
                    if (!isExtendsClass(field.getType(), T.class))
                        continue;

                    String fieldName = field.getName();
                    Object object = field.get(value);

                    if (null != object) {
                        if (waitTypeValue(strSql, object, name))
                            return;
                    }

                    if (name.equals(fieldName)) {
                        waitTypeValue(strSql, object, null);
                        return;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            cla = cla.getSuperclass();
        }
        strSql.append("null");
    }

    protected T<?> FindField(DRef<?> object, String columnName, T<SqlFiletr> outFilter) {
        T<?> obj;
        SqlFiletr filter;
        if (null != outFilter)
            outFilter.set(null);

        try {
            Class cla = object.getClass();
            while (DRef.class != cla) {
                for (Field field : cla.getDeclaredFields()) {
                    if (isExtendsClass(field.getType(), T.class)) {
                        field.setAccessible(true);
                        obj = (T<?>) field.get(object);

                        if (null != outFilter && null != obj && null != (filter = obj.getfilter(SqlFiletr.class))) {
                            outFilter.set(filter);
                            if (null != filter.getName() && columnName.toLowerCase().equals(filter.getName().toLowerCase())) {
                                return obj;
                            }
                        }
                        if (field.getName().toLowerCase().equals(columnName))
                            return obj;
                        else
                            outFilter.set(null);
                    }
                }
                cla = cla.getSuperclass();
            }
        } catch (Exception e) {
            log.error(e.toString(), e);
        }
        return null;
    }

    protected boolean isExtendsClass(Class<?> src, Class<?> dis) {
        while (null != src) {
            if (src == dis)
                return true;
            src = src.getSuperclass();
        }
        return false;
    }


    @Override
    public String toString() {
        if (null != this.strSql)
            return this.strSql.toString();
        return super.toString();
    }

    public static void setAllDebug(boolean allDebug) {
        Database.allDebug = allDebug;
    }

    public boolean isOccupy() {
        if (!isOccupy) {
            isOccupy = true;
            return false;
        }
        return true;
    }

    public void setOccupy(boolean isOccupy) {
        this.isOccupy = isOccupy;
    }

    public boolean isThread() {
        return isThread;
    }

    public void setThread(boolean isThread) {
        this.isThread = isThread;
    }

    public boolean open() throws Exception {
        if (getConnection())
            return createStatement(true);
        return false;
    }

    public boolean open(boolean autoCommit) throws Exception {
        getConnection();
        return createStatement(autoCommit);
    }

    public boolean close() {
        if (isThread) {
            isOccupy = false;
            if (null != this.pool)
                this.pool.addCount(-1);

            return closeStatement();
        }
        closeStatement();
        return closeConnection();
    }

    public void setPool(DatabasePool.Pool pool) {
        this.pool = pool;
    }

    protected void setEnumValue(TEnum typeObj, Integer val) {
        if (null == val) {
            return;
        }

        Class<Enum> className = (Class<Enum>) typeObj.getClassName();
        if (null != className) {
            Field[] fields = className.getDeclaredFields();
            for (Field field : fields) {
                if (field.isEnumConstant()) {
                    try {
                        field.setAccessible(true);
                        Object object = field.get(null);
                        if (((Enum) object).ordinal() == val) {
                            typeObj.set((Enum) object);
                            return;
                        }
                    } catch (IllegalAccessException e) {
                        log.error(e.toString(), e);
                    }
                }
            }
        }
    }
}
